package com.indeep.user.service.impl;

import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jsms.api.SendSMSResult;
import cn.jsms.api.common.SMSClient;
import cn.jsms.api.common.model.SMSPayload;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.indeep.common.core.UserSubject;
import com.indeep.common.device.JPush;
import com.indeep.common.emun.RedisKeys;
import com.indeep.common.err.InDeepException;
import com.indeep.common.result.StatusCode;
import com.indeep.user.constant.AppUserConstant;
import com.indeep.user.dto.VxAuthDTO;
import com.indeep.user.mapper.*;
import com.indeep.user.mapper.po.*;
import com.indeep.user.service.AppInvitationCodeService;
import com.indeep.user.service.ILoginService;
import com.indeep.user.service.base.BaseServiceImpl;
import com.indeep.user.utils.IndeepSmsComponent;
import com.indeep.user.utils.JWTUtils;
import com.indeep.user.utils.PhoneNumberValidator;
import com.indeep.user.utils.Random4DigitUUIDGenerator;
import com.indeep.user.vo.CheckCodeVO;
import com.indeep.user.dto.CodeDTO;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.text.DecimalFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl extends BaseServiceImpl<AppUserMapper, AppUserPO> implements ILoginService {
    @Value("${wechat.url}")
    private String wechatUrl;
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    ILoginService loginService;
    @Autowired
    private AppVxAuthMapper appVxAuthMapper;

    @Autowired
    AppUserMapper appUserMapper;

    @Autowired
    private AppUserPortraitMapper appUserPortraitMapper;


    @Autowired
    private AppInvitationCodeMapper appInvitationCodeMapper;

    @Autowired
    private AppInvitationCodeUserMapper appInvitationCodeUserMapper;

    @Autowired
    private AppExperienceLevelMapper appExperienceLevelMapper;

    @Autowired
    private AppUserSettingMapper appUserSettingMapper;

    @Autowired
    private AppVxTelBindMapper appVxTelBindMapper;

    @Autowired
    AppInvitationCodeService appInvitationCodeService;

    // 初始化发短信客户端
    private final SMSClient smsClient = IndeepSmsComponent.getSmsClient();


    @Override
    public void sendCode(String tel) {
        // 验证手机号合法性
        ArrayList<AppVxTelBindPO> appVxTelBindPOS = new ArrayList<>();
        appVxTelBindMapper.insert(appVxTelBindPOS);
        if (!PhoneNumberValidator.isValidPhoneNumber(tel)) {
            throw new InDeepException(StatusCode.TEL_WRONG_FORMAT);
        }
        String key = RedisKeys.USER_LOGIN + tel;
        if (redisTemplate.hasKey(String.format("%s%s", RedisKeys.LIMIT_ONE_MINUTE, tel))) {
            throw new InDeepException(StatusCode.WAIT_ONE_MINUTE);
        }
        if (redisTemplate.hasKey(key) && (Integer) redisTemplate.opsForValue().get(key) > 4) {
            throw new InDeepException(StatusCode.LOGIN_MORE_FIVE);
        }
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, 30, TimeUnit.MINUTES);

        if (redisTemplate.opsForHash().hasKey(RedisKeys.JUMP_SEND_CODE, tel)) {
            return;
        }
        try {
            // 构建发送短信
            SMSPayload payload = SMSPayload.newBuilder()
                    .setMobileNumber(tel)   // 手机号码
                    .setTempId(1)           // 短信模板ID
                    .setSignId(25526)       // 签名id
                    .build();
            // 发送短信 会返回msg_id
            SendSMSResult sendSMSResult = smsClient.sendSMSCode(payload);
            // 有效期5分钟
            redisTemplate.opsForValue().set(RedisKeys.MSM_MSG_ID + tel, sendSMSResult.getMessageId(), 5L, TimeUnit.MINUTES);

            // 频率限制1分钟
            redisTemplate.opsForValue().set(RedisKeys.LIMIT_ONE_MINUTE + tel, sendSMSResult.getMessageId(), 1L, TimeUnit.MINUTES);
        } catch (APIConnectionException | APIRequestException e) {
            throw new InDeepException(StatusCode.MESSAGE_FAILED_TO_SEND);
        }
    }

    @Override
    public CheckCodeVO checkCode(String tel, String code) {
        // 从redis获取msg_id
        if (!redisTemplate.opsForHash().hasKey(RedisKeys.JUMP_SEND_CODE, tel)) {
            String msg_id = (String) redisTemplate.opsForValue().get(RedisKeys.MSM_MSG_ID + tel);
            if (msg_id == null) throw new InDeepException("验证码过期", 415);

            // 检验code
            try {
                smsClient.sendValidSMSCode(msg_id, code);
            } catch (Exception e) {
                throw new InDeepException("验证码过期", 415);
            }
            // 验证成功则删除消息ID
            redisTemplate.delete(RedisKeys.MSM_MSG_ID + tel);
        }
        // 数据库中是否存在
        QueryWrapper<AppUserPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("tel", tel);
        AppUserPO appUserPO = appUserMapper.selectOne(queryWrapper);

        // 新用户 & 已注销的用户
        if (appUserPO == null || appUserPO.getStatus() == 2) {
            // redis暂存校验邀请码的资格
            String codeKey = RedisKeys.INVITATION_CODE_CERTIFICATE + tel;
            redisTemplate.opsForValue().set(codeKey, "1", 5, TimeUnit.MINUTES);

            return new CheckCodeVO(-1L, false, null, null);
        }
        // 被封禁的用户
        if (appUserPO.getStatus() == 1) {
            return new CheckCodeVO(appUserPO.getId(), true, null, null);
        }
        // 老用户
        if (appUserPO.getStatus() == 0) {
            // 生成accessToken
            Map<String, Object> accessMap = new HashMap<>();
            accessMap.put("userId", appUserPO.getId());
            accessMap.put("tel", tel);
            String accessToken = JWTUtils.getAccessToken(accessMap);

            // 把新accessToken写入redis中
            String aKey = RedisKeys.VALID_TOKEN_ACCESS + appUserPO.getId();
            redisTemplate.opsForValue().set(aKey, accessToken, JWTUtils.EXPIRE_TIME_ACCESS, TimeUnit.MILLISECONDS);

            // 生成refreshToken
            Map<String, Object> refreshMap = new HashMap<>();
            refreshMap.put("userId", appUserPO.getId());
            refreshMap.put("tel", tel);
            String refreshToken = JWTUtils.getRefreshToken(refreshMap);

            // 把新refreshToken写入redis中
            String rKey = RedisKeys.VALID_TOKEN_REFRESH + appUserPO.getId();
            redisTemplate.opsForValue().set(rKey, refreshToken, JWTUtils.EXPIRE_TIME_REFRESH, TimeUnit.MILLISECONDS);

            // 清除当前用户id的别名绑定的设备
            JPush jPush = new JPush();
            jPush.deleteAlias(appUserPO.getId().toString());
            // 返回VO
            return new CheckCodeVO(appUserPO.getId(), true, accessToken, refreshToken);
        } else return null;
    }

    @Override
    public CheckCodeVO checkInviteCode(CodeDTO dto) {
        // 数据准备
        String tel = dto.getPhoneNumber();
        // 校验用户是否存在
        boolean userExist = appUserMapper.exists(new QueryWrapper<AppUserPO>() {{
            eq("tel", tel);
            ne("status", 2);
        }});
        if (userExist) {
            CheckCodeVO checkCodeVO = new CheckCodeVO();
            checkCodeVO.setId(-3L);
            return checkCodeVO;
        }
        // 校验是否有校验邀请码的资格（通过短信验证码校验）
        String codeKey = RedisKeys.INVITATION_CODE_CERTIFICATE + tel;
        String value = String.valueOf(redisTemplate.opsForValue().get(codeKey));
        boolean inviteCodeValid = Objects.equals(value, "1");
        // 没有校验资格，返回
        if (!inviteCodeValid) {
            return null;
        }
        // 有校验资格，校验邀请码
        Integer codeId = appInvitationCodeService.checkCode(dto);
        // 邀请码剩余可邀请人数不足
        // 超过邀请码有效期
        // 邀请码校验成功
        if (codeId == null) {
            return null;
        }
        if (codeId == -1 || codeId == -2) {
            CheckCodeVO checkCodeVO = new CheckCodeVO();
            checkCodeVO.setId(Long.valueOf(codeId));
            return checkCodeVO;
        }
        // 新建用户并入库 todo new Random().nextInt() 针对用户展示的用户id后面要优化
        AppUserPO newAppUserDO = new AppUserPO(null, new Random().nextInt(), "默认用户" +
                Random4DigitUUIDGenerator.generateUUID(), 0, null, tel, AppUserConstant.AVATAR_NEUTRAL.url,
                null, 0, 0, 0, null, null, 0,
                "这个人很神秘,什么都不想告诉你", new Date(), null, null);
        // 添加微信openid
        QueryWrapper<AppVxTelBindPO> bindQw = new QueryWrapper<>();
        bindQw.eq("tel", dto.getPhoneNumber());
        List<AppVxTelBindPO> bindDOS = appVxTelBindMapper.selectList(bindQw);
        if (!bindDOS.isEmpty()) {
            // 如果不为空，返回值新增
            newAppUserDO.setVxOpenid(bindDOS.get(0).getOpenid());
        }
        appUserMapper.insert(newAppUserDO);

        // 用户画像一些基本信息
        AppUserPortraitPO appUserPortraitDO = new AppUserPortraitPO(null, newAppUserDO.getId(), 18, 0, null, new Date(), new Date());

        appUserPortraitMapper.insert(appUserPortraitDO);

        // 邀请码可邀请人数 - 1
        appInvitationCodeMapper.inviteNewUser(codeId);

        // 新增用户绑定邀请码表
        AppInvitationCodeUserPO codeUserDO = new AppInvitationCodeUserPO(newAppUserDO.getId(), codeId);
        appInvitationCodeUserMapper.insert(codeUserDO);
        // 新建用户经验值并入库
        AppExperienceLevelPO appExperienceLevelDO = new AppExperienceLevelPO(null, newAppUserDO.getId(), 0L, null, null);
        appExperienceLevelMapper.insert(appExperienceLevelDO);
        // 新建用户设置并入库
        AppUserSettingPO newUserSetting = new AppUserSettingPO(newAppUserDO.getId());
        appUserSettingMapper.insert(newUserSetting);

        // 加一个消息类型 todo 站内信内容
        // 正式见面 24

        // 生成accessToken
        Map<String, Object> map = new HashMap<>();
        map.put("userId", newAppUserDO.getId());
        map.put("tel", tel);
        String accessToken = JWTUtils.getAccessToken(map);

        // 把新accessToken写入redis中
        String akey = RedisKeys.VALID_TOKEN_ACCESS + newAppUserDO.getId();
        redisTemplate.opsForValue().set(akey, accessToken, 1, TimeUnit.DAYS);
//            redisUtils.set(akey, accessToken, 1, TimeUnit.MINUTES);

        // 生成refreshToken
        Map<String, Object> refreshMap = new HashMap<>();
        refreshMap.put("userId", newAppUserDO.getId());
        refreshMap.put("tel", tel);
        String refreshToken = JWTUtils.getRefreshToken(refreshMap);

        // 把新refreshToken写入redis中
        String rKey = RedisKeys.VALID_TOKEN_REFRESH + newAppUserDO.getId();
        redisTemplate.opsForValue().set(rKey, refreshToken, 30, TimeUnit.DAYS);

        // 当日注册用户数增加
        String key1 = "data_analysis:register_number:" + LocalDate.now().atStartOfDay(java.time.ZoneOffset.UTC).toInstant().toEpochMilli();
        redisTemplate.opsForValue().increment(key1);

        // 返回VO
        return new CheckCodeVO(newAppUserDO.getId(), false, accessToken, refreshToken);
    }

    @Override
    public void logout() {
        Long userId = UserSubject.getCurrentUserId();
        if (userId == null) {
            return;
        }
        String aKey = RedisKeys.VALID_TOKEN_ACCESS + userId;
        String rKey = RedisKeys.VALID_TOKEN_REFRESH + userId;
        redisTemplate.delete(aKey);
        redisTemplate.delete(rKey);
        // 清除当前用户id的别名绑定的设备
        JPush jPush = new JPush();
        jPush.deleteAlias(userId.toString());
    }

    @Override
    public void unsubscribe() {

        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }

        // 生成验证码，绑定id，存入redis
        int i = new Random(System.currentTimeMillis()).nextInt(1000000);
        DecimalFormat df = new DecimalFormat();
        df.applyPattern("000000");
        String code = df.format(i);

        // 有效期5分钟
        redisTemplate.opsForValue().set(RedisKeys.MSM_UNSUBSCRIBE_CODE + userId, code, 5L, TimeUnit.MINUTES);

        try {
            // 短信参数
            Map<String, String> map = new HashMap<>();
            //
//            map.put("username", appUserMapper.getUsername(appUserToken.getUserId()));
            map.put("code", code);
            map.put("time", "5");

            // 构建发送短信
            SMSPayload payload = SMSPayload.newBuilder()
                    .setMobileNumber(userTel.toString())   // 手机号码
                    .setTempId(225918)      // 短信模板ID
                    .setSignId(25526)       // 签名id
                    .setTempPara(map)
                    .build();

            // 发送短信
            smsClient.sendTemplateSMS(payload);
        } catch (APIConnectionException | APIRequestException e) {
            throw new InDeepException(StatusCode.MESSAGE_FAILED_TO_SEND);
        }
    }

    @Override
    public void unsubscribeCheck(String code) {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        // 从 redis 获取 code
        String codeKey = RedisKeys.MSM_UNSUBSCRIBE_CODE + userId;
        String codeR = redisTemplate.opsForValue().get(codeKey).toString();
        if (codeR == null) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);

        // redis校验code
        if (!codeR.equals(code)) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);

        // 验证成功则删除验证码
        redisTemplate.delete(codeKey);

        // 验证成功后执行注销流程
        // redis删除token
        String aKey = RedisKeys.VALID_TOKEN_ACCESS + userId;
        String rKey = RedisKeys.VALID_TOKEN_REFRESH + userId;
        redisTemplate.delete(aKey);
        redisTemplate.delete(rKey);
        // 用户表用户信息更新
        AppUserPO appUserDO = new AppUserPO();
        appUserDO.setId(userId);
        // 注销不修改原信息
//        appUserDO.setUsername("此用户已注销");
//        appUserDO.setAvatar(globalConstant.ossRootIndeep + "indeep/%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F_%E7%94%BB%E6%9D%BF%201.jpg");
        String newTel = "@" + userTel;
        QueryWrapper<AppUserPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("tel", newTel);
        while (appUserMapper.exists(queryWrapper)) {
            newTel = "@" + newTel;
            queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("tel", newTel);
        }
        appUserDO.setTel(newTel);
        appUserMapper.updateById(appUserDO);
    }

    @Override
    public void changeTel() {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }

        // 生成验证码，绑定id，存入redis
        int i = new Random(System.currentTimeMillis()).nextInt(1000000);
        DecimalFormat df = new DecimalFormat();
        df.applyPattern("000000");
        String code = df.format(i);

        // 有效期5分钟
        redisTemplate.opsForValue().set(RedisKeys.MSM_CHANGE_TEL_CODE + userTel, code, 5L, TimeUnit.MINUTES);

        try {
            // 短信参数
            Map<String, String> map = new HashMap<>();
            map.put("code", code);
            map.put("tel_suffix", userTel.toString().substring(7));

            // 构建发送短信
            SMSPayload payload = SMSPayload.newBuilder()
                    .setMobileNumber(userTel.toString())   // 手机号码
                    .setTempId(225917)      // 短信模板ID
                    .setSignId(25526)       // 签名id
                    .setTempPara(map)
                    .build();

            // 发送短信
            smsClient.sendTemplateSMS(payload);
        } catch (APIConnectionException | APIRequestException e) {
            throw new InDeepException(StatusCode.MESSAGE_FAILED_TO_SEND);
        }
    }

    @Override
    public void changeTelCheckCode(CodeDTO dto) {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        // 从 redis 获取 code
        String codeKey = RedisKeys.MSM_CHANGE_TEL_CODE + userId;
        String codeR = redisTemplate.opsForValue().get(codeKey).toString();
        if (codeR == null) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);

        // redis校验code
        if (!codeR.equals(dto.getCode())) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);

        // 删除验证码
        redisTemplate.delete(codeKey);

        // redis暂存允许更换手机号凭证
        String changeTelKey = RedisKeys.NEW_TEL_CERTIFICATE + userId;
        redisTemplate.opsForValue().set(changeTelKey, "1", 5, TimeUnit.MINUTES);

    }

    @Override
    public void newTel(CodeDTO dto) {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        String tel = dto.getPhoneNumber();

        // 检查redis是否存在暂存允许更换手机号凭证
        String changeTelKey = RedisKeys.NEW_TEL_CERTIFICATE + userId;
        if (!redisTemplate.hasKey(changeTelKey))
            throw new InDeepException(StatusCode.CONDITION_NOT_SATISFIED_TEL_UNVERIFIED);

        // 校验新手机号格式
        if (!PhoneNumberValidator.isValidPhoneNumber(dto.getPhoneNumber())) {
            throw new InDeepException(StatusCode.TEL_WRONG_FORMAT);
        }

        // 生成验证码，绑定id，存入redis
        int i = new Random(System.currentTimeMillis()).nextInt(1000000);
        DecimalFormat df = new DecimalFormat();
        df.applyPattern("000000");
        String code = df.format(i);

        // 有效期5分钟
        redisTemplate.opsForValue().set(RedisKeys.MSM_NEW_TEL_CODE + userId, code, 5L, TimeUnit.MINUTES);
        // 同步更换凭证过期时间并写入更换的新手机号
        redisTemplate.opsForValue().set(changeTelKey, dto.getPhoneNumber(), 5L, TimeUnit.MINUTES);

        try {
            // 短信参数
            Map<String, String> map = new HashMap<>();
            map.put("code", code);
            map.put("tel_suffix", tel.substring(7));

            // 构建发送短信
            SMSPayload payload = SMSPayload.newBuilder()
                    .setMobileNumber(tel)   // 手机号码
                    .setTempId(223613)      // 短信模板ID
                    .setSignId(25526)       // 签名id
                    .setTempPara(map)
                    .build();

            // 发送短信
            smsClient.sendTemplateSMS(payload);
        } catch (APIConnectionException | APIRequestException e) {
            throw new InDeepException(StatusCode.MESSAGE_FAILED_TO_SEND);
        }
    }

    @Override
    public void newTelCheckCode(CodeDTO dto) {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        // 从 redis 获取 code
        String codeKey = RedisKeys.MSM_NEW_TEL_CODE + userId;
        String codeR = redisTemplate.opsForValue().get(codeKey).toString();
        if (codeR == null) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);
        // 从 redis 获取 换绑的手机号
        String changeTelKey = RedisKeys.NEW_TEL_CERTIFICATE + userId;
        String tel = redisTemplate.opsForValue().get(changeTelKey).toString();

        // redis校验code
        if (!codeR.equals(dto.getCode())) throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);

        QueryWrapper<AppUserPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("tel", tel);
        if (appUserMapper.exists(queryWrapper)) {
            // 如果当前手机号存在，则返回 -2
            throw new InDeepException(StatusCode.TEL_REGISTERED);
        } else {
            // 校验成功后，清除登录状态和换绑凭证
            String aKey = RedisKeys.VALID_TOKEN_ACCESS + userId;
            String rKey = RedisKeys.VALID_TOKEN_REFRESH + userId;
            redisTemplate.delete(aKey);
            redisTemplate.delete(rKey);
            redisTemplate.delete(changeTelKey);

            // 修改用户的手机号
            AppUserPO appUserDO = new AppUserPO();
            appUserDO.setTel(tel);
            appUserDO.setId(userId);
            appUserMapper.updateById(appUserDO);
        }
    }

    @Override
    public CheckCodeVO refreshAccessToken() {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        String refreshToken = UserSubject.getCurrentToken();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        // 生成accessToken
        Map<String, Object> map = new HashMap<>();
        map.put("userId", userId);
        map.put("tel", userTel);
        String accessToken = JWTUtils.getAccessToken(map);

        // 把新accessToken写入redis中
        String aKey = RedisKeys.VALID_TOKEN_ACCESS + userId;
        redisTemplate.opsForValue().set(aKey, accessToken, JWTUtils.EXPIRE_TIME_ACCESS, TimeUnit.MILLISECONDS);

        // 写入refreshToken
        String rKey = RedisKeys.VALID_TOKEN_REFRESH + userId;
        redisTemplate.opsForValue().set(rKey, refreshToken);

        // 更新refreshToken的有效期
        // 设置过期时间为用户活跃时间 + 14天
        if (redisTemplate.hasKey(RedisKeys.USER_LATEST_ACTIVE_TIME + userId)) {
            String value = redisTemplate.opsForValue().get(RedisKeys.USER_LATEST_ACTIVE_TIME + userId).toString();
            DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime userLatestActiveTime = LocalDateTime.parse(value, localDateTimeFormatter);
            long time = Duration.between(LocalDateTime.now(), userLatestActiveTime).getSeconds() + 14 * 24 * 60 * 60;
            redisTemplate.expire(rKey, time, TimeUnit.SECONDS);
        }
        return new CheckCodeVO(userId, true, accessToken, refreshToken);
    }

    @Override
    public CheckCodeVO wechatAuth(String code) {
        // 构造获取access_token的请求
        String url = String.format(wechatUrl, code);

        OkHttpClient client = new OkHttpClient();
        Request okRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
        String okResponse;
        // 发送请求
        try (Response response = client.newCall(okRequest).execute()) {
            okResponse = response.body().string();
            //log.debug("okResponse = {}", okResponse);
        } catch (IOException e) {
            okResponse = null;
        }

        // 判断请求结果
        boolean reqStatus;
        JSONObject jsonRes;
        if (okResponse == null) {
            reqStatus = false;
        } else {
            jsonRes = JSON.parseObject(okResponse);
            reqStatus = jsonRes.containsKey("unionid");
        }

        // 用户身份解析失败，执行登录流程
        // 初始化返回结果
        CheckCodeVO res;

        // 根据请求结果执行不同的操作
        if (!reqStatus) {
            throw new InDeepException(StatusCode.INTERNAL_SERVER_ERROR);
        }
        // 解析用户信息
        VxAuthDTO vxAuthDTO = JSON.parseObject(okResponse, VxAuthDTO.class);

        // 判断用户表中是否存在
        QueryWrapper<AppUserPO> vxQw = new QueryWrapper<AppUserPO>() {{
            eq("vx_openid", vxAuthDTO.getOpenid());
        }};
        boolean exists = appUserMapper.exists(vxQw);
        if (exists) {
            // 老用户
            AppUserPO appUserDO = appUserMapper.selectOne(vxQw);

            // 生成accessToken
            Map<String, Object> accessMap = new HashMap<>();
            accessMap.put("userId", appUserDO.getId());
            accessMap.put("tel", appUserDO.getTel());
            String accessToken = JWTUtils.getAccessToken(accessMap);

            // 把新accessToken写入redis中
            String aKey = RedisKeys.VALID_TOKEN_ACCESS + appUserDO.getId();
            redisTemplate.opsForValue().set(aKey, accessToken, 1, TimeUnit.DAYS);

            // 生成refreshToken
            Map<String, Object> refreshMap = new HashMap<>();
            refreshMap.put("userId", appUserDO.getId());
            refreshMap.put("tel", appUserDO.getTel());
            String refreshToken = JWTUtils.getRefreshToken(refreshMap);

            // 把新refreshToken写入redis中
            String rKey = RedisKeys.VALID_TOKEN_REFRESH + appUserDO.getId();
            redisTemplate.opsForValue().set(rKey, refreshToken, 30, TimeUnit.DAYS);

            // 清除当前用户id的别名绑定的设备
            JPush jPush = new JPush();
            jPush.deleteAlias(appUserDO.getId().toString());

            // 返回VO
            res = new CheckCodeVO(appUserDO.getId(), true, accessToken, refreshToken);
        } else {
            // 新用户
            res = new CheckCodeVO(-2L, false, null, null) {{
                setOpenid(vxAuthDTO.getOpenid());
            }};
        }

        // 入库微信授权信息表
        AppVxAuthPO appVxAuthPO = new AppVxAuthPO(vxAuthDTO);
        appVxAuthMapper.insertOrUpdate(appVxAuthPO);

        return res;
    }

    @Override
    public void authAndBind(String code) {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        // 判断当前用户是否有执行敏感操作的权限
        String key = RedisKeys.SENSITIVE_OPERATIONS_PASS + userId;
        boolean pass = redisTemplate.hasKey(key);
        if (!pass) throw new InDeepException(StatusCode.NEED_AUTH);

        // 构造获取access_token的请求
        String url = String.format(wechatUrl, code);

        OkHttpClient client = new OkHttpClient();
        Request okRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
        String okResponse;
        // 发送请求
        try (Response response = client.newCall(okRequest).execute()) {
            okResponse = response.body().string();
            //log.debug("okResponse = {}", okResponse);
        } catch (IOException e) {
            okResponse = null;
        }

        // 判断请求结果
        boolean reqStatus;
        JSONObject jsonRes;
        if (okResponse == null) {
            reqStatus = false;
        } else {
            jsonRes = JSON.parseObject(okResponse);
            reqStatus = jsonRes.containsKey("unionid");
        }

        // 用户身份解析成功，执行绑定流程
        if (reqStatus) {
            // 请求成功
            // 解析微信认证信息
            VxAuthDTO vxAuthDTO = JSON.parseObject(okResponse, VxAuthDTO.class);
            // 入库微信授权信息表
            AppVxAuthPO appVxAuthDO = new AppVxAuthPO(vxAuthDTO);
            appVxAuthMapper.insertOrUpdate(appVxAuthDO);

            // 判断用户表中是否存在
            QueryWrapper<AppUserPO> vxQw = new QueryWrapper<AppUserPO>() {{
                eq("vx_openid", vxAuthDTO.getOpenid());
            }};
            boolean exists = appUserMapper.exists(vxQw);
            if (exists) {
                // 已有用户绑定此微信
                throw new InDeepException(StatusCode.WX_BOUND);
            } else {
                // 无用户绑定此微信
                // 绑定当前用户
                AppUserPO currentUser = new AppUserPO();
                currentUser.setId(userId);
                currentUser.setVxOpenid(vxAuthDTO.getOpenid());
                appUserMapper.updateById(currentUser);
            }
        } else {
            // 请求失败
            throw new InDeepException(StatusCode.WX_AUTH_FAIL);
        }

    }

    @Override
    public void unbindWechat() {
        Long userId = UserSubject.getCurrentUserId();
        Long userTel = UserSubject.getCurrentUserTel();
        // 从token中获取手机号
        if (userTel == 0 || userId == 0) {
            throw new InDeepException(StatusCode.LOGIN_ERROR);
        }
        AppUserPO userDO = appUserMapper.selectById(userId);
        if (userDO.getVxOpenid() == null) throw new InDeepException(StatusCode.NO_WX_AUTH);

        // 判断当前用户是否有执行敏感操作的权限
        String key = RedisKeys.SENSITIVE_OPERATIONS_PASS + userId;
        boolean pass = redisTemplate.hasKey(key);
        if (!pass) throw new InDeepException(StatusCode.NEED_AUTH);
        // 解绑微信
        LambdaUpdateWrapper<AppUserPO> appUserPOUpdateWrapper = new LambdaUpdateWrapper<>();
        appUserPOUpdateWrapper.set(AppUserPO::getVxOpenid, null);
        appUserPOUpdateWrapper.eq(AppUserPO::getId, userId);
        appUserMapper.update(appUserPOUpdateWrapper);
    }

    @Override
    public void bindTel(CodeDTO dto) {
        // 1.验证手机号是否已被绑定
        QueryWrapper<AppUserPO> telQw = new QueryWrapper<AppUserPO>() {{
            eq("tel", dto.getPhoneNumber());
        }};
        boolean exists = appUserMapper.exists(telQw);
        if (exists) {
            throw new InDeepException(StatusCode.TEL_REGISTERED);
        }
        loginService.sendCode(dto.getPhoneNumber());
    }

    @Override
    public CheckCodeVO bindTelCheck(CodeDTO dto) {
        // 1.数据校验
        // 校验openid是否存在
        QueryWrapper<AppVxAuthPO> vxAuthQw = new QueryWrapper<>();
        vxAuthQw.eq("openid", dto.getOpenid());
        boolean openidExists = appVxAuthMapper.exists(vxAuthQw);
        if (!openidExists) throw new InDeepException(StatusCode.VX_AUTH_NOT_EXIST);
        // 校验是否已有用户绑定该openid
        QueryWrapper<AppUserPO> userQw = new QueryWrapper<>();
        userQw.eq("vx_openid", dto.getOpenid());
        userQw.in("status", 0, 1);
        boolean banded = appUserMapper.exists(userQw);
        if (banded) throw new InDeepException(StatusCode.WX_BOUND);

        // 调用校验验证码的方法
        CheckCodeVO res = checkCode(dto.getPhoneNumber(), dto.getCode());
        // 根据返回值确定是否需要做特殊操作
        if (res != null && res.getId() == -1L) {
            // 新用户，需要邀请码

            // 新增和微信绑定的手机号的缓存
            AppVxTelBindPO appVxTelBindDO = new AppVxTelBindPO(dto.getPhoneNumber(), dto.getOpenid());
            appVxTelBindMapper.insertOrUpdate(appVxTelBindDO);

            return res;
        }
        if (res == null) {
            // 验证码校验失败
            throw new InDeepException(StatusCode.CODE_VERIFICATION_FAILED);
        }
        if (res.getRefreshToken() == null) {
            // 用户被封禁
            throw new InDeepException(StatusCode.USER_BANDED);
        }

        // 老用户
        AppUserPO userDO = appUserMapper.selectById(res.getId());
        if (userDO.getVxOpenid() == null) {
            // 绑定微信
            userDO.setVxOpenid(dto.getOpenid());
            appUserMapper.updateById(userDO);
            return res;
        } else {
            return new CheckCodeVO(-402L);
        }
    }


}
